import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import javax.swing.filechooser.*;

final class WorldFrame extends JFrame {
   private final World world;
   public WorldFrame(World ww) {
      world=ww;
      setTitle(world.toString());
      setSize(480, 480);
      setLayout(new BorderLayout());
      add(world, BorderLayout.CENTER);
      add(createToolBar(), BorderLayout.PAGE_START);
      setJMenuBar(createMenus());
      setDefaultCloseOperation(DISPOSE_ON_CLOSE);
      setVisible(true);
   }
   @Override public void dispose() {
      if(world.isRunning()) {
         world.stop();
      }
      super.dispose();
      worldFrames.remove(this);
      if(worldFrames.isEmpty()) System.exit(0); //>>> may be dangerous
   }
   public World getWorld() {
      return world;
   }
   private JToolBar createToolBar() {
      JToolBar toolBar=new JToolBar("Control");
      //toolBar.setRollover(true);
      //toolBar.setFloatable(false);
      JButton runStopButton=new JButton("Run ");
      runStopButton.addActionListener(runStopHandler);

      JButton stepButton=new JButton("Step");
      stepButton.addActionListener(stepHandler);

      JButton speedUpButton=new JButton("Speed Up");
      speedUpButton.addActionListener(speedUpHandler);

      JButton slowDownButton=new JButton("Slow Down");
      slowDownButton.addActionListener(slowDownHandler);

      JButton shiftLeftButton=new JButton("Left");
      shiftLeftButton.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.shiftLeft();
            world.repaint();
         }
      });
      JButton shiftRightButton=new JButton("Right");
      shiftRightButton.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.shiftRight();
            world.repaint();
         }
      });
      JButton shiftUpButton=new JButton("Up");
      shiftUpButton.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.shiftUp();
            world.repaint();
         }
      });
      JButton shiftDownButton=new JButton("Down");
      shiftDownButton.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.shiftDown();
            world.repaint();
         }
      });
      
      toolBar.add(runStopButton);
      toolBar.add(stepButton);
      toolBar.add(speedUpButton);
      toolBar.add(slowDownButton);
      toolBar.addSeparator();
      toolBar.add(shiftLeftButton);
      toolBar.add(shiftRightButton);
      toolBar.add(shiftUpButton);
      toolBar.add(shiftDownButton);
      return toolBar;
   }
   private ActionListener runStopHandler=new ActionListener() {
      public void actionPerformed(ActionEvent e) {
         if(!AbstractButton.class.isInstance(e.getSource())) {
            throw new RuntimeException();
         }
         AbstractButton src=(AbstractButton)e.getSource();
         if(world.isRunning()) {
            world.stop();
            src.setText("Run ");
         } else {
            world.log();
            world.start();
            src.setText("Stop");
         }
      }
   };
   private ActionListener stepHandler=new ActionListener() {
      public void actionPerformed(ActionEvent e) {
         world.stepAll();
         repaint();
      }
   };
   private ActionListener speedUpHandler=new ActionListener() {
      public void actionPerformed(ActionEvent e) {
         world.speedUp();
         //if(!world.canSpeedUp()) fasterItem.setEnabled(false);
      }
   };
   private ActionListener slowDownHandler=new ActionListener() {
      public void actionPerformed(ActionEvent e) {
         world.slowDown();
         //if(world.canSpeedUp()) fasterItem.setEnabled(true);
      }
   };
   private static final java.util.List<WorldFrame> worldFrames=new LinkedList<WorldFrame>();
   public static void create(final int p, final int q) {
      javax.swing.SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            final WorldFrame wf=new WorldFrame(new World2D(Rule2D.LIFE, p, q));
            if(!worldFrames.isEmpty()) {
               //Dimension screen=Toolkit.getDefaultToolkit().getScreenSize();
               WorldFrame lastFrame=worldFrames.get(worldFrames.size()-1);
               final int lastX=lastFrame.getX();
               final int lastY=lastFrame.getY();
               int newX=lastX+20;
               int newY=lastY+20;
               //if(newX>=screen.getWidth()) newX=0;
               //if(newY>=screen.getHeight()) newY=0;
               wf.setLocation(newX, newY);
            }
            worldFrames.add(wf);
         }
      });
   }
   private JMenuBar createMenus() {
      final JMenuItem newItem=new JMenuItem("New...");
      newItem.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            String dimStr=JOptionPane.showInputDialog(WorldFrame.this, "Please input a dimenstion, ex. \"50*50\".");
            if(dimStr==null) return;
            if(!dimStr.matches("\\d*+\\*\\d*+")) {
               JOptionPane.showMessageDialog(WorldFrame.this, "Invalid Dimension Format!");
               return;
            }
            String pStr=dimStr.substring(0, dimStr.indexOf("*"));
            String qStr=dimStr.substring(dimStr.indexOf("*")+1, dimStr.length());
            WorldFrame.create(Integer.parseInt(pStr), Integer.parseInt(qStr));
            /*
            System.out.println("Please input dimenstion(p, q):");
            Scanner cin=new Scanner(System.in);
            WorldFrame.create(cin.nextInt(), cin.nextInt());
            System.out.println("New world created.");
            */
         }
      });

      final JMenuItem importItem=new JMenuItem("Import...");
      importItem.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            //world.fromText("0010\n1001\n10a5\n");
            JFileChooser chooser = new JFileChooser();
            //FileNameExtensionFilter filter = new FileNameExtensionFilter( "Cellular Automata States", "cas");
            //chooser.setFileFilter(filter);
            int returnVal = chooser.showOpenDialog(WorldFrame.this);
            if(returnVal == JFileChooser.APPROVE_OPTION) {
               try {
                  world.stop();
                  world.fromFile(chooser.getSelectedFile());
                  setTitle(world.toString());
               } catch(IOException xpt) {
                  xpt.printStackTrace();
               }
            }
            world.repaint();
         }
      });
      final JMenuItem exportItem=new JMenuItem("Export...");
      exportItem.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            //System.out.println(world.toText());
            JFileChooser chooser = new JFileChooser();
            //FileNameExtensionFilter filter = new FileNameExtensionFilter( "Cellular Automata States", "cas");
            //chooser.setFileFilter(filter);
            int returnVal = chooser.showSaveDialog(WorldFrame.this);
            if(returnVal == JFileChooser.APPROVE_OPTION) {
               try {
                  File file=chooser.getSelectedFile();
                  if(file.exists()) {
                     int res=JOptionPane.showConfirmDialog(
                        WorldFrame.this, String.format(
                           "%s already exists, Override it?", 
                           file.getAbsolutePath()));
                     if(res==JOptionPane.YES_OPTION) {
                        world.toFile(file);
                     } else { //NO or CANCEL
                        return; //>>>?
                     }
                  } else {
                     world.toFile(file);
                  }
               } catch(IOException xpt) {
                  xpt.printStackTrace();
               }
            }
         }
      });
      final JMenuItem closeItem=new JMenuItem("Close");
      closeItem.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            if(world.isRunning()) {
               world.stop();
            }
            dispose();
         }
      });
      final JMenu fileMenu=new JMenu("File");
      fileMenu.add(newItem);
      fileMenu.add(importItem);
      fileMenu.add(exportItem);
      fileMenu.add(closeItem);

      final JMenuItem previous=new JMenuItem("Previous");
      final JMenuItem next=new JMenuItem("Next");
      //final JMenuItem log=new JMenuItem("Log");
      //log.addActionListener(new ActionListener() {
      //   public void actionPerformed(ActionEvent e) {
      //      world.log();
      //      //previous.setEnabled(true);
      //   }
      //});
      previous.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.stop();
            if(world.hasPast()) {
               world.previous();
               //next.setEnabled(true);
            } else {
               JOptionPane.showMessageDialog(WorldFrame.this, "No Previous Status");
            }
            world.repaint();
            //if(!world.hasPast()) previous.setEnabled(false);
         }
      });
      //previous.setEnabled(false);
      next.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.stop();
            if(world.hasFuture()) {
               world.next();
               //previous.setEnabled(true);
            } else {
               JOptionPane.showMessageDialog(WorldFrame.this, "No Next Status");
            }
            world.repaint();
            //if(!world.hasFuture()) next.setEnabled(false);
         }
      });
      //next.setEnabled(false);
      final JMenuItem random=new JMenuItem("Generate Random Map");
      random.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.generateRandomMap(0.1);
            repaint();
         }
      });
      final JMenuItem clear=new JMenuItem("Clear Map");
      clear.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.clear();
            repaint();
         }
      });
      final JMenu editMenu=new JMenu("Edit");
      //editMenu.add(log);
      editMenu.add(previous);
      editMenu.add(next);
      editMenu.add(random);
      editMenu.add(clear);

      /*
      final JMenu controlMenu=new JMenu("Control");
      final JMenuItem toggleItem=new JMenuItem("Run");
      toggleItem.addActionListener(runStopHandler);
      final JMenuItem stepItem=new JMenuItem("Step");
      stepItem.addActionListener(stepHandler);



      final JMenuItem fasterItem=new JMenuItem("Faster");
      fasterItem.addActionListener(speedUpHandler);
      final JMenuItem slowerItem=new JMenuItem("Slower");
      slowerItem.addActionListener(slowDownHandler);
      final JMenu speedMenu=new JMenu("Speed");
      speedMenu.add(fasterItem);
      speedMenu.add(slowerItem);

      controlMenu.add(toggleItem);
      controlMenu.add(speedMenu);
      controlMenu.add(stepItem);
      //controlMenu.add(rules);
      */

      JMenu ruleMenu=new JMenu("Rule");
      final JMenuItem ruleMgrItem=new JMenuItem("Rule Manager...");
      ruleMgrItem.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            ruleMgr.setVisible(true);
         }
      });
      //ruleMgrItem.setEnabled(false);
      ruleMenu.add(ruleMgrItem);

      /*
      final JMenu rules=new JMenu("Rules");
      final JMenuItem lifeRule=new JMenuItem("Life Rule");
      lifeRule.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.setRule(Rule2D.LIFE);
         }
      });
      rules.add(lifeRule);
      final JMenuItem logicRule=new JMenuItem("Logic Rule");
      logicRule.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.setRule(Rule2D.LOGIC);
         }
      });
      rules.add(logicRule);
      final JMenuItem brainRule=new JMenuItem("Brain Rule");
      brainRule.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.setRule(Rule2D.BRAIN);
         }
      });
      rules.add(brainRule);

      final JMenuItem fadersRule=new JMenuItem("Faders Rule");
      fadersRule.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.setRule(Rule2D.FADERS);
         }
      });
      rules.add(fadersRule);
      final JMenuItem customRule=new JMenuItem("Custom...");
      customRule.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            System.out.println("Please input a rule(s/b/g):");
            Scanner cin=new Scanner(System.in);
            String s1=cin.next();
            String s2=cin.next();
            int g=cin.nextInt();
            Rule r=new Rule2D(s1, s2, g);
            world.setRule(r);
            System.out.printf("Rule %s created.\n", r);
            setTitle(world.toString());
         }
      });
      rules.add(customRule);
      ruleMenu.add(rules);
      */

      final JMenuItem changeColors=new JMenuItem("Random Color");
      changeColors.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.changeColors();
            repaint();
         }
      });
      final JCheckBoxMenuItem showGridItem=
         new JCheckBoxMenuItem("Show Grid", world.showGrid);
      showGridItem.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.showGrid=!world.showGrid;
            showGridItem.setState(world.showGrid);
            repaint();
         }
      });
      final JCheckBoxMenuItem keepAspectRatioItem=
         new JCheckBoxMenuItem("Keep Aspect Ratio", world.keepAspectRatio);
      keepAspectRatioItem.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            world.keepAspectRatio=!world.keepAspectRatio;
            repaint();
         }
      });
      final JMenu viewMenu=new JMenu("View");
      viewMenu.add(changeColors);
      viewMenu.add(showGridItem);
      viewMenu.add(keepAspectRatioItem);


      final JMenuItem aboutItem=new JMenuItem("About...");
      aboutItem.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            //System.out.printf("%s by %s\n", Main.APP_NAME, Main.AUTHOR_NAME); //>>> %n doesn't work???
            JOptionPane.showMessageDialog(WorldFrame.this, String.format("%s by %s\n", Main.APP_NAME, Main.AUTHOR_NAME));
         }
      });
      final JMenu helpMenu=new JMenu("Help");
      helpMenu.add(aboutItem);

      final JMenuBar menuBar=new JMenuBar();
      menuBar.add(fileMenu);
      menuBar.add(editMenu);
      //menuBar.add(controlMenu);
      menuBar.add(ruleMenu);
      menuBar.add(viewMenu);
      menuBar.add(helpMenu);
      return menuBar;
   }
   
   private RuleManager ruleMgr=new RuleManager(this);
}
